
package jp.moisture.othello.board;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Board {

    public static final int[][] POSITION_POINT = {
            {
                    120, -24, 0, -1, -1, 0, -24, 120
            }, {
                    -24, -60, -3, -3, -3, -3, -60, -24
            },
            {
                    0, -3, 0, -1, -1, 0, -3, 0
            }, {
                    -1, -3, 0, -1, -1, 0, -3, -1
            }, {
                    -1, -3, 0, -1, -1, 0, -3, -1
            }, {
                    0, -3, 0, -1, -1, 0, -3, 0
            },
            {
                    -24, -60, -3, -3, -3, -3, -60, -24
            }, {
                    120, -24, 0, -1, -1, 0, -24, 120
            }
    };
    private static final Map<Integer, Integer> PUTTABLE_MAP = new HashMap<Integer, Integer>() {
        {
            put(0, -50);
            put(1, -30);
            put(2, -10);
            put(3, -7);
            put(4, -4);
            put(5, -1);
            put(6, 0);
            put(7, 1);
            put(8, 2);
        }

    };

    public class State {

        public static final int WON = 90000000;
        private static final int IMMUTABLE_RATIO = 3;
        private static final int POTEANTIAL_IMMUTABLE_RATIO = 1;
        private static final int PUTTABLE_RATIO = 1;
        private static final int POSITION_RATIO = 1;
        int mCountBlack;
        int mCountWhite;
        int mPositionBlack;
        int mPositionWhite;
        int mImmutableBlack;
        int mImmutableWhite;
        int mPotentialImmutableBlack;
        int mPotentialImmutableWhite;
        int mPutableCellPoints[] = new int[2];

        int mPutablePoint;
        int mImmutablePoint;
        int mPotentialImmutablePoint;
        int mPositionPoint;
        int mPoint;

        public State() {
            calculate();
            summary();

            int larger = mStoneNum + 1;
            int smaller = mSize * mSize - mStoneNum + 1;
            int average = mSize * mSize / 2 + 1;
            mPutablePoint = (calcPutablePoint(mPutableCellPoints[Stone.BLACK]) - calcPutablePoint(mPutableCellPoints[Stone.WHITE])) * PUTTABLE_RATIO * average;
            mImmutablePoint = (mImmutableBlack - mImmutableWhite) * IMMUTABLE_RATIO * larger;
            mPotentialImmutablePoint = (mPotentialImmutableBlack - mPotentialImmutableWhite) * POTEANTIAL_IMMUTABLE_RATIO * larger;
            mPositionPoint = (mPositionBlack - mPositionWhite) * POSITION_RATIO * smaller;
            mPoint = mPutablePoint + mImmutablePoint + mPotentialImmutablePoint + mPositionPoint;
        }

        private int calcPutablePoint(int num) {
            if (num >= 9) {
                return 3;
            }
            return PUTTABLE_MAP.get(num);
        }

        private void calculate() {
            mPutableCellPoints[Stone.BLACK] = findPutableCells(Stone.BLACK).size();
            mPutableCellPoints[Stone.WHITE] = findPutableCells(Stone.WHITE).size();

            if (mCells[0][0].mStone == null && mCells[0][mSize - 1].mStone == null && mCells[mSize - 1][0].mStone == null
                    && mCells[mSize - 1][mSize - 1].mStone == null) {
                return;
            }

            // 不定石チェック１
            int half = mSize / 2;
            for (int depth = 0; depth < half; depth++) {
                int s = depth;
                int e = mSize - depth - 1;
                int l = mSize - (depth * 2) - 1;
                boolean found = false;
                for (int color = Stone.BLACK; color <= Stone.WHITE; color++) {
                    found |= checkLineImmutable(color, s, s, l, 1, 0);
                    found |= checkLineImmutable(color, e, s, l, -1, 0);
                    found |= checkLineImmutable(color, s, e, l, 1, 0);
                    found |= checkLineImmutable(color, e, e, l, -1, 0);

                    found |= checkLineImmutable(color, s, s, l, 0, 1);
                    found |= checkLineImmutable(color, s, e, l, 0, -1);

                    found |= checkLineImmutable(color, e, s, l, 0, 1);
                    found |= checkLineImmutable(color, e, e, l, 0, -1);
                }
                if (!found) {
                    break;
                }
            }

            for (int x = 0; x < mSize; x++) {
                for (int y = 0; y < mSize; y++) {
                    checkWeight(x, y);
                }
            }

            // // 不定石チェック2
            // boolean[] lineX = new boolean[mSize];
            // boolean[] lineY = new boolean[mSize];
            // boolean[] lineC1 = new boolean[mSize * 2];
            // boolean[] lineC2 = new boolean[mSize * 2];
            // for (int x = 0; x < mSize; x++) {
            // lineX[x] = checkFull(x, 0, 0, 1);
            // }
            // for (int y = 0; y < mSize; y++) {
            // lineY[y] = checkFull(0, y, 1, 0);
            // }
            // int tx = 0;
            // int ty = 0;
            // int crossHarfSize = mSize - 1;
            // int crossSize = mSize * 2 - 1;
            // for (int l = 0; l < crossSize; l++) {
            // // System.out.println("(" + tx + "," + ty + ")");
            // lineC1[l] = checkFull(tx, ty, -1, 1);
            // if (l < crossHarfSize) {
            // tx++;
            // } else {
            // ty++;
            // }
            // }
            // // System.out.println();
            // tx = 0;
            // ty = 7;
            // for (int l = 0; l < crossSize; l++) {
            // lineC2[l] = checkFull(tx, ty, 1, 1);
            // if (l < crossHarfSize) {
            // ty--;
            // } else {
            // tx++;
            // }
            // }
            // for (int x = 0; x < mSize; x++) {
            // for (int y = 0; y < mSize; y++) {
            // if (lineX[x] && lineY[y] && lineC1[x + y] && lineC2[x + (7 - y)])
            // {
            // mCells[x][y].mStone.mImmutable = true;
            // /*
            // } else if (mStoneNum >= 63 && !mCells[x][y].mStone.mImmutable) {
            // Cell cell = mCells[x][y];
            // Stone stone = mCells[x][y].mStone;
            // System.out.println("cell " + cell.toStringData());
            // System.out.println("lineX " + Arrays.toString(lineX));
            // System.out.println("lineY " + Arrays.toString(lineY));
            // System.out.println("lineC1 " + Arrays.toString(lineC1));
            // System.out.println("lineC2 " + Arrays.toString(lineC2));
            // System.out.println("aaa " + lineX[x] + ":" + lineY[y] + ":" +
            // lineC1[x+y] + ":" + lineC2[(7-x)+y]);
            // System.out.println("stone " + stone);
            // */
            // }
            // }
            // }
            // 不定石チェック3
            for (int x = 0; x < mSize; x++) {
                for (int y = 0; y < mSize; y++) {
                    Stone stone = mCells[x][y].mStone;
                    if (stone != null && !stone.mImmutable) {
                        stone.mImmutable = isImmutable(x, y);
                    }
                }
            }
            for (int y = mSize - 1; y <= 0; y--) {
                for (int x = mSize - 1; x <= 0; x--) {
                    Stone stone = mCells[x][y].mStone;
                    if (stone != null && !stone.mImmutable) {
                        stone.mImmutable = isImmutable(x, y);
                    }
                }
            }
        }

        private final void summary() {

            // 数をサマリ
            for (int x = 0; x < mSize; x++) {
                for (int y = 0; y < mSize; y++) {
                    Stone stone = mCells[x][y].mStone;
                    if (stone != null) {
                        if (stone.mColor == Stone.BLACK) {
                            mCountBlack++;
                            mPositionBlack += POSITION_POINT[x][y];
                            if (stone.mImmutable) {
                                mImmutableBlack++;
                            } else if (stone.mPotentialImmutable) {
                                mPotentialImmutableBlack++;
                            }
                        } else {
                            mCountWhite++;
                            mPositionWhite += POSITION_POINT[x][y];
                            if (stone.mImmutable) {
                                mImmutableWhite++;
                            } else if (stone.mPotentialImmutable) {
                                mPotentialImmutableWhite++;
                            }
                        }
                    }

                }
            }
        }

        final void checkWeight(int x, int y) {
            Stone stone = mCells[x][y].mStone;
            if (stone == null || stone.mImmutable) {
                return;
            }
            SideState[] sides = new SideState[8];

            sides[0] = checkSideWeight(x, y, -1, 0);
            sides[1] = checkSideWeight(x, y, 1, 0);
            sides[2] = checkSideWeight(x, y, 0, -1);
            sides[3] = checkSideWeight(x, y, 0, 1);
            sides[4] = checkSideWeight(x, y, -1, -1);
            sides[5] = checkSideWeight(x, y, 1, 1);
            sides[6] = checkSideWeight(x, y, -1, 1);
            sides[7] = checkSideWeight(x, y, 1, -1);
            // int weight = Integer.MAX_VALUE;
            // // // int weight = 0;
            // // boolean cantTrun = true;
            // // for (SideState side : sides) {
            // // cantTrun &= side.cantTurn();
            // // weight = Math.min(weight, side.getWeight());
            // // // weight += side.getWeight();
            // // }
            //
            // weight = Math.min(weight, Math.max(sides[0].getWeight(),
            // sides[1].getWeight()));
            // weight = Math.min(weight, Math.max(sides[2].getWeight(),
            // sides[3].getWeight()));
            // weight = Math.min(weight, Math.max(sides[4].getWeight(),
            // sides[5].getWeight()));
            // weight = Math.min(weight, Math.max(sides[6].getWeight(),
            // sides[7].getWeight()));
            // stone.mWeight = weight;
            // if (cantTrun) {
            if (sides[0].isFull() && sides[1].isFull() && sides[2].isFull() && sides[3].isFull() && sides[4].isFull() && sides[5].isFull() && sides[6].isFull()
                    && sides[7].isFull()) {
                stone.mImmutable = true;
                return;
            }

            boolean immutable = (!sides[0].canExsistOther() || !sides[1].canPutOther()) && (!sides[1].canExsistOther() || !sides[0].canPutOther())
                    && (!sides[2].canExsistOther() || !sides[3].canPutOther()) && (!sides[3].canExsistOther() || !sides[2].canPutOther())
                    && (!sides[4].canExsistOther() || !sides[5].canPutOther()) && (!sides[5].canExsistOther() || !sides[4].canPutOther())
                    && (!sides[6].canExsistOther() || !sides[7].canPutOther()) && (!sides[7].canExsistOther() || !sides[6].canPutOther());

            // System.out.println(sides[0].canExsistOther());
            // System.out.println(sides[0].canPutOther());
            // System.out.println(sides[1].canExsistOther());
            // System.out.println(sides[1].canPutOther());

            if (immutable) {
                // if ((!sides[0].canPutOther() && !sides[1].canPutOther()) &&
                // (!sides[2].canPutOther() && !sides[3].canPutOther()) &&
                // (!sides[4].canPutOther() && !sides[5].canPutOther()) &&
                // (!sides[6].canPutOther() && !sides[7].canPutOther())) {
                stone.mPotentialImmutable = true;
            } else {
                stone.mPotentialImmutable = false;
            }
        }

        private final SideState checkSideWeight(int x, int y, int stepX, int stepY) {
            SideState side = new SideState();
            Stone currentStone = mCells[x][y].mStone;
            for (int tx = x, ty = y; tx < mSize && ty < mSize && tx >= 0 && ty >= 0; tx += stepX, ty += stepY) {
                Stone stone = mCells[tx][ty].mStone;
                if (stone == null) {
                    side.mEmptyNum++;
                } else {
                    if (side.mEmptyNum > 0) {
                        break;
                    } else if (currentStone.mColor != stone.mColor) {
                        side.mNestNum++;
                    }
                    currentStone = stone;
                }
            }
            return side;
        }

        // private final boolean checkFull(int x, int y, int stepX, int stepY) {
        // for (int tx = x, ty = y; tx < mSize && ty < mSize && tx >= 0 && ty >=
        // 0; tx += stepX, ty += stepY) {
        // Stone stone = mCells[tx][ty].mStone;
        // if (stone == null) {
        // return false;
        // }
        // }
        // return true;
        // }

        private final boolean checkLineImmutable(int color, int startX, int startY, int loop, int stepX, int stepY) {
            boolean found = false;
            for (int x = startX, y = startY; loop > 0; x += stepX, y += stepY, loop--) {
                Stone stone = mCells[x][y].mStone;
                if (stone == null || stone.mColor != color) {
                    break;
                } else if (isImmutable(color, x, y)) {
                    stone.mImmutable = true;
                    found = true;
                }
            }
            return found;
        }

        public boolean isCold() {
            return mCountBlack == 0 || mCountWhite == 0;
        }

        public int getPutableCount(int color) {
            return mPutableCellPoints[color];
        }

        public int getImmutableCount(int color) {
            return color == Stone.BLACK ? mImmutableBlack : mImmutableWhite;
        }

        public int getPotentialImmutableCount(int color) {
            return color == Stone.BLACK ? mPotentialImmutableBlack : mPotentialImmutableWhite;
        }

        public int getPositionRawPoint(int color) {
            return color == Stone.BLACK ? mPositionBlack : mPositionWhite;
        }

        public int getPoint(int color) {
            int won = 0;
            if (mCompleted) {
                won = isWon(color) ? WON : -WON;
            } else if (isWideMarginWon(color)) {
                won = WON;
            } else if (isWideMarginWon(Stone.otherColor(color))) {
                won = -WON;
            } else if (isWideMarginPossibleWon(color)) {
                won = WON / 2;
            } else if (isWideMarginPossibleWon(Stone.otherColor(color))) {
                won = -WON / 2;
            }
            return won + (color == Stone.BLACK ? mPoint : -mPoint);
        }

        public int getPutablePoint(int color) {
            return color == Stone.BLACK ? mPutablePoint : -mPutablePoint;
        }

        public int getImmutablePoint(int color) {
            return color == Stone.BLACK ? mImmutablePoint : -mImmutablePoint;
        }

        public int getPotentialImmutablePoint(int color) {
            return color == Stone.BLACK ? mPotentialImmutablePoint : -mPotentialImmutablePoint;
        }

        public int getPositionPoint(int color) {
            return color == Stone.BLACK ? mPositionPoint : -mPositionPoint;
        }

        public final boolean isWon(int color) {
            return (mCompleted && isAdvantage(color)) || (color == Stone.BLACK ? mCountWhite == 0 : mCountBlack == 0);
        }

        public final boolean isLoose(int color) {
            return mCompleted && !isAdvantage(color) || (color == Stone.BLACK ? mCountBlack == 0 : mCountWhite == 0);
        }

        public final boolean isWideMarginWon(int color) {
            int half = (mSize * mSize) / 2;
            return (color == Stone.BLACK && mImmutableBlack > half) || (color == Stone.WHITE && mImmutableWhite > half);
        }

        public final boolean isWideMarginPossibleWon(int color) {
            int half = (mSize * mSize) / 2;
            return (color == Stone.BLACK && (mImmutableBlack + mPotentialImmutableBlack) > half)
                    || (color == Stone.WHITE && (mImmutableWhite + mPotentialImmutableWhite) > half);
        }

        public final boolean isAdvantage(int color) {
            return color == Stone.BLACK ? mCountBlack > mCountWhite : mCountBlack < mCountWhite;
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append(String.format("T:[%2d:(%2d/%2d)] BW[%2d/%2d] P:%d(PU:%d[%d/%d],IM:%d[%d/%d],PIM:%d[%d/%d],PO:%d[%d/%d])", mTurnCount, getStoneCount(),
                    getEmptyCellCount(), mCountBlack, mCountWhite, getPoint(Stone.BLACK), getPutablePoint(Stone.BLACK), getPutableCount(Stone.BLACK),
                    getPutableCount(Stone.WHITE), getImmutablePoint(Stone.BLACK), getImmutableCount(Stone.BLACK), getImmutableCount(Stone.WHITE),
                    getPotentialImmutablePoint(Stone.BLACK), getPotentialImmutableCount(Stone.BLACK), getPotentialImmutableCount(Stone.WHITE),
                    getPositionPoint(Stone.BLACK), getPositionRawPoint(Stone.BLACK), getPositionRawPoint(Stone.WHITE)));
            if (mCompleted) {
                sb.append('\n');
                String winner = isAdvantage(Stone.BLACK) ? "Black" : "White";
                sb.append(winner);
                sb.append(" won!\n");
            }
            return sb.toString();
        }
    }

    static class SideState {
        int mNestNum;
        int mEmptyNum;

        // int mWeight;

        final boolean cantTurn() {
            return mEmptyNum == 0 || mNestNum >= mEmptyNum;
        }

        final boolean canPutOther() {
            return mEmptyNum != 0 && mNestNum < mEmptyNum;
        }

        final boolean exsistOther() {
            return mNestNum > 0;
        }

        final boolean canExsistOther() {
            return canPutOther() || exsistOther();
        }

        final boolean isFull() {
            return mEmptyNum == 0;
        }

        int getWeight() {
            if (mEmptyNum == 0) {
                return 5;
            }
            return mNestNum;
        }
    }

    private static final String[] LINE_TEXTS = new String[] {
            "１", "２", "３", "４", "５", "６", "７", "８"
    };
    private static final List<Stone> EMPTY_STONE = new ArrayList<Stone>();

    private int mSize;
    Cell[][] mCells;
    private int mTurnCount;
    private int mStoneNum;
    protected boolean mCompleted;
    private State mState;
    private Map<Integer, List<Cell>> mPutableCellCache = new HashMap<>();

    public Board(int size) {
        this(size, true);
    }

    public Board(int size, boolean initialize) {
        // if (size % 2 == 1) {
        // throw new IllegalArgumentException("size required even. : " + size);
        // }
        mSize = size;
        mCells = new Cell[mSize][mSize];
        if (initialize) {
            init();
        }
    }

    public String getId() {
        StringBuilder sb = new StringBuilder();
        for (int y = 0; y < mSize; y++) {
            for (int x = 0; x < mSize; x++) {
                Stone stone = mCells[x][y].mStone;
                if (stone == null) {
                    sb.append("00");
                } else if (stone.mColor == Stone.BLACK) {
                    sb.append("01");
                } else {
                    sb.append("11");
                }
            }
        }

        long[] subIds = new long[4];
        int size = sb.length() / subIds.length;
        for (int i = 0; i < subIds.length; i++) {
            String text = sb.substring(i * size, (i + 1) * size);
            System.out.println(text);
            System.out.println(text.length());
            subIds[i] = Long.parseLong(text, 2);
        }

        char[] a = {
                '亜', '亜', '亜', '亜'
        };
        a[0] += subIds[0];
        a[1] += subIds[1];
        a[2] += subIds[2];
        a[3] += subIds[3];
        // char a = '亜' + (subIds[0] * subIds[1]);
        return new String(a);
    }

    public Board(Board board) {
        mSize = board.mSize;
        mTurnCount = board.mTurnCount;
        mStoneNum = board.mStoneNum;
        mCells = new Cell[mSize][mSize];
        for (int x = 0; x < mSize; x++) {
            for (int y = 0; y < mSize; y++) {
                mCells[x][y] = new Cell(board.mCells[x][y]);
            }
        }
    }

    public int getSize() {
        return mSize;
    }

    private void init() {
        for (int x = 0; x < mSize; x++) {
            for (int y = 0; y < mSize; y++) {
                mCells[x][y] = new Cell(x, y);
            }
        }

        if (mSize % 2 == 0) {
            int harf = mSize / 2;
            mCells[harf - 1][harf - 1].setStone(Stone.WHITE);
            mCells[harf][harf - 1].setStone(Stone.BLACK);
            mCells[harf - 1][harf].setStone(Stone.BLACK);
            mCells[harf][harf].setStone(Stone.WHITE);
            mStoneNum = 4;
        } else {
            int harf = mSize / 2;
            mCells[harf - 1][harf - 1].setStone(Stone.WHITE);
            mCells[harf][harf - 1].setStone(Stone.BLACK);
            mCells[harf + 1][harf - 1].setStone(Stone.WHITE);

            mCells[harf - 1][harf].setStone(Stone.BLACK);
            mCells[harf][harf].setStone(Stone.WHITE);
            mCells[harf + 1][harf].setStone(Stone.BLACK);

            mCells[harf - 1][harf + 1].setStone(Stone.WHITE);
            mCells[harf][harf + 1].setStone(Stone.BLACK);
            mCells[harf + 1][harf + 1].setStone(Stone.WHITE);
            mStoneNum = 9;
        }
    }

    public int getTurnCount() {
        return mTurnCount;
    }

    public int getStoneCount() {
        return mStoneNum;
    }

    public Cell getCell(int x, int y) {
        return mCells[x][y];
    }

    public boolean isFull() {
        return mStoneNum >= (mSize * mSize);
    }

    public final List<Cell> findPutableCells(int color) {
        if (mPutableCellCache.containsKey(color)) {
            return mPutableCellCache.get(color);
        }
        List<Cell> cells = new ArrayList<Cell>();
        for (int x = 0; x < mSize; x++) {
            for (int y = 0; y < mSize; y++) {
                if (canPut(x, y, color)) {
                    cells.add(mCells[x][y]);
                }
            }
        }
        mPutableCellCache.put(color, cells);
        return cells;
    }

    private final boolean canPut(int x, int y, int color) {
        if (mCells[x][y].mStone != null) {
            return false;
        }
        boolean can = canPut(x, y, 1, 0, color) || canPut(x, y, -1, 0, color) || canPut(x, y, 0, 1, color) || canPut(x, y, 0, -1, color)
                || canPut(x, y, 1, 1, color) || canPut(x, y, -1, 1, color) || canPut(x, y, 1, -1, color) || canPut(x, y, -1, -1, color);
        return can;
    }

    private final boolean canPut(int x, int y, int stepX, int stepY, int color) {
        boolean exsistsOther = false;
        for (int tx = x + stepX, ty = y + stepY; tx < mSize && ty < mSize && tx >= 0 && ty >= 0; tx += stepX, ty += stepY) {
            Stone stone = mCells[tx][ty].mStone;
            if (stone == null) {
                return false;
            } else if (stone.mColor != color) {
                exsistsOther = true;
            } else if (exsistsOther) {
                return true;
            } else {
                return false;
            }
        }
        return false;
    }

    public boolean exisitsStone(int x, int y) {
        return mCells[x][y].mStone != null;
    }

    @Override
    public String toString() {
        State state = getState();
        StringBuilder sb = new StringBuilder();
        sb.append("　");
        for (int x = 0; x < mSize; x++) {
            sb.append(LINE_TEXTS[x]);
        }
        sb.append('\n');

        int color = mTurnCount % 2 == 0 ? Stone.BLACK : Stone.WHITE;
        List<Cell> putableCells = findPutableCells(color);

        for (int y = 0; y < mSize; y++) {
            sb.append(LINE_TEXTS[y]);
            for (int x = 0; x < mSize; x++) {
                Cell cell = mCells[x][y];
                if (cell.mStone != null) {
                    sb.append(cell);
                } else {
                    boolean find = false;
                    for (Cell putableCell : putableCells) {
                        if (putableCell == cell) {
                            sb.append("・");
                            find = true;
                        }
                    }
                    if (!find) {
                        sb.append("　");
                    }
                }
            }

            sb.append(LINE_TEXTS[y]);
            sb.append('\n');
        }
        sb.append("　");
        for (int x = 0; x < mSize; x++) {
            sb.append(LINE_TEXTS[x]);
        }
        sb.append('\n');
        sb.append(state.toString());
        sb.append("\nPUT:");
        for (Cell putableCell : putableCells) {
            sb.append(putableCell.toStringData());
        }

        sb.append('\n');
        return sb.toString();
    }

    public final void setStone(int x, int y, int color) {
        putStone(mCells[x][y], color);
    }

    public final void putStone(Cell cell, int color) {
        mTurnCount++;
        mState = null;
        mPutableCellCache.clear();
        if (cell == null) {
            return;
        }
        putStoneWithTurn(cell, 1, 0, color);
        putStoneWithTurn(cell, -1, 0, color);
        putStoneWithTurn(cell, 0, 1, color);
        putStoneWithTurn(cell, 0, -1, color);
        putStoneWithTurn(cell, 1, 1, color);
        putStoneWithTurn(cell, -1, 1, color);
        putStoneWithTurn(cell, 1, -1, color);
        putStoneWithTurn(cell, -1, -1, color);
        cell.setStone(color);
        mStoneNum++;
    }

    private final void putStoneWithTurn(Cell cell, int stepX, int stepY, int color) {
        List<Stone> stones = findTurnable(cell, stepX, stepY, color);
        for (Stone stone : stones) {
            stone.turn();
        }
    }

    private final List<Stone> findTurnable(Cell cell, int stepX, int stepY, int color) {
        int x = cell.mX;
        int y = cell.mY;
        List<Stone> stones = new ArrayList<Stone>();
        for (int tx = x + stepX, ty = y + stepY; tx < mSize && ty < mSize && tx >= 0 && ty >= 0; tx += stepX, ty += stepY) {
            Stone stone = mCells[tx][ty].mStone;
            if (stone == null) {
                return EMPTY_STONE;
            } else if (stone.mColor != color) {
                stones.add(stone);
            } else if (!stones.isEmpty()) {
                return stones;
            } else {
                return EMPTY_STONE;
            }
        }
        return EMPTY_STONE;
    }

    public State getState() {
        if (mState == null) {
            // System.out.println("state");
            mState = new State();
        }
        return mState;
    }

    private final boolean isImmutable(int color, int x, int y) {
        return (isRoundImmutable(color, x - 1, y - 0) || isRoundImmutable(color, x + 1, y + 0))
                && (isRoundImmutable(color, x - 0, y - 1) || isRoundImmutable(color, x + 0, y + 1))
                && (isRoundImmutable(color, x - 1, y - 1) || isRoundImmutable(color, x + 1, y + 1))
                && (isRoundImmutable(color, x - 1, y + 1) || isRoundImmutable(color, x + 1, y - 1));
    }

    private final boolean isRoundImmutable(int color, int x, int y) {
        if (x < 0 || y < 0 || x >= mSize || y >= mSize) {
            return true;
        }
        // try {
        // Cell cell = mCells[x][y];
        Stone stone = mCells[x][y].mStone;
        if (stone == null) {
            return false;
        }
        return stone.mImmutable && stone.mColor == color;
        // } catch (IndexOutOfBoundsException e) {
        // return true;
        // }
    }

    private final boolean isImmutable(int x, int y) {
        return isRoundImmutable(x - 1, y - 0) && isRoundImmutable(x + 1, y + 0) && isRoundImmutable(x - 0, y - 1) && isRoundImmutable(x + 0, y + 1)
                && isRoundImmutable(x - 1, y - 1) && isRoundImmutable(x + 1, y + 1) && isRoundImmutable(x - 1, y + 1) && isRoundImmutable(x + 1, y - 1);
    }

    private final boolean isRoundImmutable(int x, int y) {
        try {
            Cell cell = mCells[x][y];
            if (cell.mStone == null) {
                return false;
            }
            return cell.mStone.mImmutable;
        } catch (IndexOutOfBoundsException e) {
            return true;
        }
    }

    public final int getEmptyCellCount() {
        return mSize * mSize - mStoneNum;
    }

}
